Dwarves on Rails

Rails is a web framework written in the Ruby programming language. It implements the Model-View-Controller architectural pattern. Several facets of Rails, including its use of code generators and reliance on reading the database schema, make it ideal for Agile development.

Once again I will be using my Dwarf Manager test case. I have previously used this test case with Java JDBC, Java EJB, and C# .NET.

Getting Started

All files for this project are available here. Directory paths on the following indicate locations within the tar file.

The first step in creating a database-backed Rails application is the creation of an initial database schema. My Postgresql files are db/Dwarf_Schema.sql and db/Dwarf_Load.sql. The table and column names have been modified slightly from previous versions to match Rails conventions. When names match Rails conventions little configuration is necessary -- a major benefit compared to the lengthy XML configuration files of Java EJB.

After an initial schema is completed the Rails generators can be run. I ran:

Models

Rails ActiveRecord implements an Object- Relational Mapping layer. The generators initially created model classes Dwarf and Mountain. Initially these classes contain no predefined attributes -- instead Rails queries the database at runtime to determine the available columns. Rails does not retrive foreign key information, so the developer annotates the generated classes with belongs_to, has_one, has_and_belongs_to_many, etc. to indicate One-to-One, One-to-Many, and Many-to-Many associations. Note ActiveRecord supports Many-to-Many assocations directly so join tables like visits in the Data Model above are invisible to the developer. The developer also never sees foreign keys; instead they can use expressions like aDwarf.mountain.name to traverse to and access desired fields directly.

Controllers

The generators initially produced controllers DwarvesController and MountainsController. These controllers are generated with default method implementations for index, list, show, new, create, edit, update, and destroy. These actions are accessible from the web server. In the URL
http://localhost:3000/dwarves/edit/5
http://localhost:3000 identifies the application, dwarves identifies the controller, edit identifies the action (method), and 5 is a parameter for the action (here the dwarf_id).

Views

The generators initially produced views list.rhtml, _form.rhtml, edit.rhtml, new.rhtml and show.rhtml for both dwarves and mountains. The views are .rhtml pages, which are html pages with embedded Ruby code executed by the server (much like Java Server Pages, ASP, PHP, etc.). The initially generated versions support the basic CRUD (Create, Read, Update, Delete) actions. They are then edited to support displaying and setting foreign key fields, etc. After adding validation annotations to the model classes (see Dwarf) the views will automatically return validation errors:

AJAX

The Rails framework includes built-in support for AJAX (Asynchronous JavaScript and XML). I added an example of AJAX to the Listing dwarves page (the first screen shot on this web page). When the visits hyperlink is clicked it is replaced with the results of a query to find all the mountains the dwarf has visited. This is done without reloading the web page. Instead a XMLHttpRequest is made and the <div> tag is replaced with the results. Rails provides a server-side ruby method link_to_remote plus a client-side javascript class Ajax.Updater which does this automatically. In dwarves list.rhtml it is implemented by line
<div id="div<%= dwarf.id %>"><%= link_to_remote("visits", :update => "div" + dwarf.id.to_s, 
  :url => { :action => :visits, :id => dwarf }) %></div>
In the client browser this looks like:
<div id="div3"><a href="#" onclick="new Ajax.Updater('div3', '/dwarves/visits/3', 
  {asynchronous:true, evalScripts:true}); return false;">visits</a></div>
When the hyperlink is clicked it invokes the (manually added) visits action in DwarvesController, which returns the data via the (manually created) dwarves view visits.rhtml.

SOAP Web Services

Rails Action Web Service provides basic server-side support for SOAP and XML-RPC based web services. The first step is once again generation: The APIs in DwarfServiceApi are annotated to indicate the parameter names and types and return types. Note complex types such as arrays of Dwarf and Mountain objects can be used. Once this is done the web server can return a WSDL document describing the service to interested clients:
http://localhost:3000/dwarf_service/service.wsdl
returns service.wsdl.

The web service implementations are added to DwarfServiceController.

The Ruby programming language includes a SOAP implementation which dynamically retrieves and uses a WSDL document. With this it is easy to construct a test client soapTest.rb which retrieves lists of dwarves and mountains.

Performance

By default Rails find(:all) methods implement the classic N+1 query anti-pattern where one query is done to find all the rows of a base table and then one query is done for each desired foreign-key-linked row in other tables. Many object-relational mapping systems have difficulties generating queries using joins. With a (often outer) join most of these N+1 queries can be relaced with a single query.

Rails does provide a solution for this problem. find methods accept an additional optional parameter :include which lists the additional associations which should be preloaded when the base query is performed. Outer joins will be generated. I modified the list action on Mountain to use include:

  def list
    # @mountain_pages, @mountains = paginate :mountain, :per_page => 10
    @mountains = Mountain.find(:all, :include => [:king])
  end
Here is a Postgresql trace before and after. Note we still see the queries to dynamically determine the available columns in the table.

Rails also provides automatic transaction support. I have not investigated it yet.